#!/usr/bin/python

## Copyright 2011 Tiago Espinha

## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at

##    http://www.apache.org/licenses/LICENSE-2.0

## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.

import string, sys, os, re, subprocess, shutil, time, datetime, tarfile

# ONLY TO BE USED ON LINUX. REQUIRES patch, svn & ant

def runtest(trdir,patchname,resultspath):
    sJarDir = trdir + '/jars/sane/'
    sClasspath = sJarDir+'/derby.jar:'+sJarDir+'/derbytools.jar:'+sJarDir+'/derbyrun.jar:'+sJarDir+'/derbynet.jar:'+sJarDir+'/derbyclient.jar:'+sJarDir+'/derbyTesting.jar:'+trdir+'/tools/java/jakarta-oro-2.0.8.jar:'+trdir+'/tools/java/junit.jar'
    
    aArgs = ['java','-Xmx128m','-Xmx1024m',
             '-XX:MaxPermSize=512m','-classpath',sClasspath,
             'junit.textui.TestRunner',
             'org.apache.derbyTesting.functionTests.suites.All']

    sOutputDir = '/tmp/daemon/test-out/'
    shutil.rmtree(sOutputDir, ignore_errors=True)
    os.mkdir(sOutputDir)

    o = open(sOutputDir+'stdout.txt','w')
    e = open(sOutputDir+'stderr.txt','w')
    
    oResult = subprocess.Popen(aArgs, cwd=sOutputDir, stdout=o, stderr=e)

    aOutput = oResult.communicate()

    fullrespath = resultspath+'/'+patchname+'.tgz'
    if os.path.exists(fullrespath):
        os.remove(fullrespath)
        
    tar = tarfile.open(fullrespath,'w:gz')
    tar.add(sOutputDir)
    tar.close()

    shutil.rmtree(sOutputDir, ignore_errors=True)
    
    return

def cleantrunk(trdir):
    '''Cleans an svn working copy to its pristine state (warning: removes ALL extraneous files)'''
    
    aArgs = ['svn','status']
    oResult = subprocess.Popen(aArgs, cwd=trdir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    aOutput = oResult.communicate()

    if len(aOutput[0]) > 0:
        crumbs = aOutput[0].split('\n')

        for c in crumbs:
            if c[0:1] == '!' or len(c[0:1])==0:
                continue
            os.remove(trdir+'/'+c[8:])

    aArgs = ['svn','update']
    oResult = subprocess.Popen(aArgs, cwd=trdir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    oResult.communicate()
    
    return

def applypatch(dir, patch):
    '''Applies the file pointed by "patch" in "dir" - ONLY works if patch binary is present!'''
    
    aArgs = ['patch','-p0','-d'+dir,'-i'+patch]
    oResult = subprocess.Popen(aArgs, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    aOutput = oResult.communicate()
    
    return oResult.returncode==0

def getdate():
    '''Convenience encapsulation for Pythons datetime functions. Returns current time and date.'''
    return datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S");

def checktrunk(sTrunkPath):
    '''Check whether the given path is an svn working copy and is clean. Returns True or False.'''
    
    aArgs = ['svn','status',sTrunkPath]
    oResult = subprocess.Popen(aArgs, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    aOutput = oResult.communicate()

    # If it isn't a working copy, might as well quit.
    if aOutput[1].find('warning:') > -1:
        print getdate() + ' # ERROR # Provided Derby trunk is not a working copy'
        sys.exit(0)

    # is the trunk dirty? I want to be very strict here
    if len(aOutput[0])>0:
        return False
    
    return True

def compiletrunk(sTrunkPath):
    '''Compiles the trunk in sTrunkPath with Derby\'s ant targets: clobber, all, buildjars'''
    
    aCommands = [['ant','clobber'],
                 ['ant','all'],
                 ['ant','buildjars']]

    for aArgs in aCommands:
        oResult = subprocess.Popen(aArgs,cwd=sTrunkPath,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        aOutput = oResult.communicate()

        # Careful here, I'm hoping ant returns !=0 returncodes on build failure
        if oResult.returncode!=0:
            print getdate() + ' # ERROR # Could not compile Derby!'
            print 'Something bad happened:'+ aOutput[1]
            sys.exit(0)

    return

def recoverpatch(sTmpPath, sOutPath, sPatchPath, sPatchName):
    os.rename(sTmpPath+'/'+sPatchName,sOutPath+'/COULD-NOT-RUN-'+sPatchName+'.tgz')
    os.remove(sPatchPath+'/.running.patch')

def main():
    if len(sys.argv)<4:
        print 'Usage: patchDaemon <clean Derby trunk> <patches path> <test output dir>'
        sys.exit(0)

    if not os.path.isdir(sys.argv[1]) \
    or not os.path.isdir(sys.argv[2]) \
    or not os.path.isdir(sys.argv[3]):
        print 'Provided paths are not directories!'
        sys.exit(0)
    
    sDerbyPath = sys.argv[1]
    sPatchPath = sys.argv[2]
    sOutPath = sys.argv[3]

    aPatchList = os.listdir(sPatchPath)

    oMatcher = re.compile(".*\.(diff|patch)-id-[0-9]+$")
    aPatchList = filter(oMatcher.search, aPatchList)

    # No patches exist - exit
    if len(aPatchList) <= 0:
        sys.exit(0)

    # There is a patch running - exit
    if '.running.patch' in aPatchList[0]:
        sys.exit(0)

    if not checktrunk(sDerbyPath):
        cleantrunk(sDerbyPath)
    
    print getdate() + ' # Starting patch # ' + aPatchList[0]
    
    # If tmp dir doesn't exist, create it
    sTmpDir = '/tmp/daemon/'
    if not os.path.isdir(sTmpDir):
        os.mkdir(sTmpDir)

    # Move patch into place
    os.rename(sPatchPath+'/'+aPatchList[0],sTmpDir+'/'+aPatchList[0]);

    # "touch" .running.patch
    f = open(sPatchPath+'/.running.patch','w')
    f.close()

    print getdate() + ' # Applying patch'

    # Could not apply patch! Clean trunk
    if not applypatch(sDerbyPath, sTmpDir+'/'+aPatchList[0]):
        print getdate() + ' # Could not apply patch - cleaning and retrying'

        cleantrunk(sDerbyPath)

        # If this happens, die. Otherwise folders are unclean.
        if not applypatch(sDerbyPath, sTmpDir+'/'+aPatchList[0]):
            print getdate() + ' # ERROR # Could not apply patch after cleaning trunk'
            recoverpatch(sTmpDir, sOutPath, sPatchPath,aPatchList[0])
            sys.exit(0)
            

    # Compile trunk
    print getdate() + ' # Compiling trunk'
    compiletrunk(sDerbyPath)

    # Run tests
    print getdate() + ' # Starting test run'
    runtest(sDerbyPath,aPatchList[0],sOutPath)

    # Move patch back, with a name change & remove "lock"
    os.rename(sTmpDir+'/'+aPatchList[0],sPatchPath+'/'+aPatchList[0]+'-done');
    os.remove(sPatchPath+'/.running.patch')

    print getdate() + ' # All done! Stopping...'
    
if __name__ == "__main__":
    main()
